package it.unibo.refolding.common;

import java.awt.geom.Point2D;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

import com.google.common.base.Function;
import com.google.common.base.Preconditions;

public class LinearFunction implements Function<Double, Double> {

    public final double slope, intercept;

    private static final Pattern pattern = Pattern.compile(String.format(
        Locale.ROOT, "^\\s*\\((%1$s)\\s*,\\s*(%1$s)\\)\\s*,\\s*\\((%1$s)\\s*,\\s*(%1$s)\\)\\s*$",
        CommonThings.FP_REGEX));

    public LinearFunction(double slope, double intercept) {
        this.slope = slope;
        this.intercept = intercept;
    }

    @Override
    public int hashCode() {
        return new HashCodeBuilder().append(slope).append(intercept).toHashCode();
    }

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof LinearFunction)) {
            return false;
        }
        LinearFunction other = (LinearFunction) obj;
        return slope == other.slope & intercept == other.intercept;
    }

    public double evaluate(double x) {
        return slope * x + intercept;
    }

    public double rmsd(Point2D.Double[] data) {
        double sum = 0;
        for (Point2D.Double p : data) {
            double eval = evaluate(p.x);
            double diff = Math.abs(eval - p.y);
            sum += diff * diff;
        }
        return Math.sqrt(sum / data.length);
    }

    public Point2D.Double intersection(LinearFunction other) {
        double x = (intercept - other.intercept) / (other.slope - slope);
        double y = evaluate(x);
        return new Point2D.Double(x, y);
    }

    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this, ToStringStyle.SIMPLE_STYLE);
    }

    public double computeXGivenY(double y) {
        return (y - intercept) / slope;
    }

    public static LinearFunction lineWithSlopeThroughPoint(double slope, Point2D.Double p) {
        return new LinearFunction(slope, p.y - slope * p.x);
    }

    public static LinearFunction lineThroughPoints(Point2D.Double a, Point2D.Double b) {
        double slope = (b.y - a.y) / (b.x - a.x);
        double intercept = a.y - slope * a.x;
        return new LinearFunction(slope, intercept);
    }

    @Override
    public Double apply(Double input) {
        return evaluate(input);
    }
    
    public static LinearFunction parseTwoPointsStringLogX(String s) {
        Matcher matcher = pattern.matcher(s);
        Preconditions.checkArgument(matcher.matches());
        return lineThroughPoints(
            new Point2D.Double(Math.log(Double.parseDouble(matcher.group(1))), Double.parseDouble(matcher
                .group(2))),
            new Point2D.Double(Math.log(Double.parseDouble(matcher.group(3))), Double.parseDouble(matcher
                .group(4))));
    }

    public static LinearFunction parseTwoPointsString(String s) {
        Matcher matcher = pattern.matcher(s);
        Preconditions.checkArgument(matcher.matches());
        return lineThroughPoints(
            new Point2D.Double(Double.parseDouble(matcher.group(1)), Double.parseDouble(matcher
                .group(2))),
            new Point2D.Double(Double.parseDouble(matcher.group(3)), Double.parseDouble(matcher
                .group(4))));
    }
}
